////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors.
//  All Rights Reserved. The following is Source Code and is subject to all
//  restrictions on such code as contained in the End User License Agreement
//  accompanying this product.
//
////////////////////////////////////////////////////////////////////////////////

package mx.controls.treeClasses
{

import flash.utils.Dictionary;
import mx.collections.ArrayCollection;
import mx.collections.CursorBookmark;
import mx.collections.ICollectionView;
import mx.collections.IList;
import mx.collections.IViewCursor;
import mx.collections.XMLListCollection;
import mx.controls.menuClasses.IMenuDataDescriptor;
import mx.utils.UIDUtil;

/**
 *  The DefaultDataDescriptor class provides a default implementation for
 *  accessing and manipulating data for use in controls such as Tree and Menu.
 *
 *  This implementation handles e4x XML and object nodes in similar but different
 *  ways. See each method description for details on how the method
 *  accesses values in nodes of various types.
 *
 *  This class is the default value of the Tree, Menu, MenuBar, and
 *  PopUpMenuButton control <code>dataDescriptor</code> properties.
 *
 *  @see mx.controls.treeClasses.ITreeDataDescriptor
 *  @see mx.controls.menuClasses.IMenuDataDescriptor
 *  @see mx.controls.Menu
 *  @see mx.controls.Menu Bar
 *  @see mx.controls.PopUpMenuButton
 *  @see mx.controls.Tree
 */
public class DefaultDataDescriptor implements ITreeDataDescriptor, IMenuDataDescriptor
{
	include "../../core/Version.as";

	/**
	 *  Constructor
	 */
	public function DefaultDataDescriptor()
	{
		super();
	}

	/**
     *  @private
     */
	private var XMLListCollectionCache:Dictionary = new Dictionary(true);

    /**
     *  Provides access to a node's children. Returns a collection
     *  of children if they exist. If the node is an Object, the method
	 *  returns the contents of the object's <code>children</code> field as
	 *  an ArrayCollection.
	 *  If the node is XML, the method returns an XMLListCollection containing
	 *  the child elements.
     *
	 *  @param node The node object currently being evaluated.
     *  @param model The collection that contains the node; ignored by this class.
     *  @return An object containing the children nodes.
     */
	public function getChildren(node:Object, model:Object=null):ICollectionView
	{
		if (node == null)
			return null;
			
		var children:*;
		var childrenCollection:ICollectionView;
		
		//first get the children based on the type of node. 
		if (node is XML)
		{
			children = node.*;
		}
		else if (node is Object)
		{
			//we'll try the default children property
			try
			{
				children = node.children;
			}
			catch (e:Error)
			{
			}
		}
		//then wrap children in ICollectionView if necessary
		if (children is ICollectionView)
		{
			childrenCollection = ICollectionView(children);
		}
		else if (children is Array)
		{
			childrenCollection = new ArrayCollection(children);
		}
		else if (children is XMLList)
		{
			var oldCollection:XMLListCollection = XMLListCollectionCache[node];
			if (!oldCollection)
			{
				childrenCollection =  new XMLListCollection(children);
				XMLListCollectionCache[node] = childrenCollection;
			}
			else
			{
				childrenCollection = oldCollection;
				XMLListCollection(childrenCollection).source = children;
			}
		}
		else
		{
			var childArray:Array = new Array(children);
			if (childArray != null)
			{
				childrenCollection =  new ArrayCollection(childArray);
			}
		}
		return childrenCollection;
	}
	
	/**
	 *  Returns true if the node actually has children. 
	 * 
	 *  @param node The node object currently being evaluated.
     *  @param model The collection that contains the node; ignored by this class.
     *  @return boolean indicating if this node currently has children
	 */
	public function hasChildren(node:Object, model:Object=null):Boolean
	{
		if (node == null) 
			return false;
			
		//This default impl can't optimize this call to getChildren
		//since we can't make any assumptions by type.  Custom impl's
		//can probably avoid this call and reduce the number of calls to 
		//getChildren if need be. 
		var children:ICollectionView = getChildren(node, model);
		try 
		{
			if (children.length > 0)
				return true;
		}
		catch (e:Error)
		{
		}
		return false;
	}

    /**
     *  Tests a node for termination.
	 *  Branches are non-terminating but are not required to have any leaf nodes.
	 *  If the node is XML, returns <code>true</code> if the node has children
	 *  or a <code>true isBranch</code> attribute.
	 *  If the node is an object, returns <code>true</code> if the node has a
	 *  (possibly empty) <code>children</code> field.
     *
     *  @param node The node object currently being evaluated.
     *  @param model The collection that contains the node; ignored by this class.
     *  @return boolean indicating if this node is non-terminating
     */
	public function isBranch(node:Object, model:Object=null):Boolean
	{
		if (node == null)
			return false;
			
		var branch:Boolean = false;
			
		if (node is XML)
		{
			var childList:XMLList = node.children();
			//accessing non-required e4x attributes is quirky
			//but we know we'll at least get an XMLList
			var branchFlag:XMLList = node.@isBranch;
			//check to see if a flag has been set
			if (branchFlag.length() == 1)
			{
				//check flag and return (this flag overrides termination status)
				if (branchFlag[0] == "true")
			    	branch = true;
			}
			//since no flags, we'll check to see if there are children
			else if (childList.length() != 0)
			{
				branch = true;
			}
		}
		else if (node is Object)
		{
			try
			{
				if (node.children != undefined)
				{
					branch = true;
				}
			}
			catch (e:Error)
			{
			}
		}
		return branch;
	}

    /**
     *  Returns a node's data.
	 *  Currently returns the entire node.
     *
     *  @param node The node object currently being evaluated.
     *  @param model The collection that contains the node; ignored by this class.
     *  @return The node.
     */
	public function getData(node:Object, model:Object=null):Object
	{
		return Object(node);
	}

    /**
     *  Add a child node to a node at the specified index. 
	 *  This implementation does the following:
	 * 
	 *  <ul>
	 *      <li>If the <code>parent</code> parameter is null or undefined,
	 *          inserts the <code>child</code> parameter at the 
	 *          specified index in the collection specified by <code>model</code>
	 *          parameter.
	 *      </li>
	 *      <li>If the <code>parent</code> parameter has a <code>children</code>
	 *          field or property, the method adds the <code>child</code> parameter
	 *          to it at the <code>index</code> parameter location.
	 *          In this case, the <code>model</code> parameter is not required.
	 *     </li>
	 *     <li>If the <code>index</code> value is greater than the collection 
	 *         length or number of children in the parent, adds the object as
	 *         the last child.
	 *     </li>
	 * </ul>
     *
	 *  @param parent The node object that will parent the child
	 *  @param newChild The node object that will be parented by the node
	 *  @param index The 0-based index of where to put the child node relative to the parent
     *  @param model The entire collection that this node is a part of
     *  @return true if successful
     */
	public function addChildAt(parent:Object, newChild:Object, index:int, model:Object=null):Boolean
	{
		if (!parent)
		{
			try
			{
				if (index > model.length)
					index = model.length;
				IList(model).addItemAt(newChild, index);
				return true;
			}
			catch (e:Error)
			{
			}
		}
		else 
		{
			var children:IList = IList(getChildren(parent, model));
			try
			{
				if (index > children.length)
					index = children.length;
				children.addItemAt(newChild, index);
				return true;
			}
			catch (e:Error)
			{
			}
		}
		return false;
	}

	/**
     *  Removes the child node from a node at the specified index.
 	 *  If the <code>parent</code> parameter is null 
	 *  or undefined, the method uses the <code>model</code> parameter to 
	 *  access the child; otherwise, it uses the <code>parent</code> parameter
	 *  and ignores the <code>model</code> parameter.
    *
	 *  @param parent The node object that currently parents the child node
	 *  @param child The node that is being removed
	 *  @param index The 0-based index of  the child node to remove relative to the parent
     *  @param model The entire collection that this node is a part of
     *  @return true if successful
     */
	public function removeChildAt(parent:Object, child:Object, index:int, model:Object=null):Boolean
	{
		//handle top level where there is no parent
		if (!parent)
		{
			try
			{
				if (index > model.length)
					index = model.length;
				model.removeItemAt(index);
				return true;
			}
			catch (e:Error)
			{
			}
		}
		else
		{
			var children:IList = IList(getChildren(parent, model));
			try
			{
				if (index > children.length)
					index = children.length;
				children.removeItemAt(index);
				return true;
			}
			catch (e:Error)
			{
			}
		}
		return false;
	}

	/**
     *  Returns the type identifier of a node.
	 *  This method is used by menu-based controls to determine if the
	 *  node represents a separator, radio button,
	 *  a check box, or normal item.
     *
	 *  @param node The node object for which to get the type.
	 *  @return  the value of the <code>type</code> attribute or field,
	 *  or the empty string if there is no such field.
     */
	public function getType(node:Object):String
	{
		if (node is XML)
		{
			return String(node.@type);
		}
		else if (node is Object)
		{
			try
			{
				return String(node.type);
			}
			catch (e:TypeError)
			{
			}
		}
		return "";
	}

	/**
     *  Returns whether the node is enabled.
	 *  This method is used by menu-based controls.
     *
	 *  @param node The node for which to get the status.
	 *  @return the value of the node's <code>enabled</code>
	 *  attribute or field, or <code>true</code> if there is no such
	 *  entry or the value is not false.
     */
	public function isEnabled(node:Object):Boolean
	{
		var enabled:*;
		if (node is XML)
		{
			enabled = node.@enabled;
			if (enabled[0] == false)
				return false;
		}
		else if (node is Object)
		{
			try
			{
				return !("false" == String(node.enabled))
			}
			catch (e:Error)
			{
			}
		}
		return true;
	}

	/**
     *  Sets the value of the field or attribute in the data provider
	 *  that identifies whether the node is enabled.
	 *  This method sets the value of the node's <code>enabled</code>
	 *  attribute or field.
	 *  This method is used by menu-based controls.
     *
	 *  @param node The node for which to set the status.
	 *  @param value Whether the node is enabled.
     */
	public function setEnabled(node:Object, value:Boolean):void
	{
		if (node is XML)
		{
			node.@enabled = value;
		}
		else if (node is Object)
		{
			try
			{
				node.enabled = value;
			}
			catch (e:TypeError)
			{
			}
		}
	}

	/**
     *  Returns whether the node is toggled.
	 *  This method is used by menu-based controls.
     *
	 *  @param node The node for which to get the status.
	 *  @return The value of the node's <code>toggled</code>
	 *  attribute or field, or <code>false</code> if there is no such
	 *  entry.
     */
	public function isToggled(node:Object):Boolean
	{
		if (node is XML)
		{
			var toggled:* = node.@toggled;
			if (toggled[0] == true)
				return true;
		}
		else if (node is Object)
		{
			try
			{
				return Boolean(node.toggled);
			}
			catch (e:TypeError)
			{
			}
		}
		return false;
	}

	/**
     *  Sets the value of the field or attribute in the data provider
	 *  that identifies whether the node is toggled.
	 *  This method sets the value of the node's <code>toggled</code>
	 *  attribute or field.
	 *  This method is used by menu-based controls.
     *
	 *  @param node The node for which to set the status.
	 *  @param value Whether the node is toggled.
     */
	public function setToggled(node:Object, value:Boolean):void
	{
		if (node is XML)
		{
			node.@toggled = value;
		}
		else if (node is Object)
		{
			try
			{
				node.toggled = value;
			}
			catch (e:TypeError)
			{
			}
		}
	}

	/**
     *  Returns the name of the radio button group to which
	 *  the node belongs, if any.
	 *  This method is used by menu-based controls.
     *
	 *  @param node The node for which to get the group name.
	 *  @return The value of the node's <code>groupName</code>
	 *  attribute or field, or an empty string if there is no such
	 *  entry.
     */
	public function getGroupName(node:Object):String
	{
		if (node is XML)
		{
			return node.@groupName;
		}
		else if (node is Object)
		{
			try
			{
				return node.groupName;
			}
			catch (e:TypeError)
			{
			}
		}
		return "";
	}

}

}
